package com.qwc.blog.security;

import com.qwc.blog.security.handler.JwtAuthenticationEntryPoint;
import com.qwc.blog.security.authentication.JwtAuthenticationTokenFilter;
import com.qwc.blog.security.handler.MyAccessDeniedHandler;
import com.qwc.blog.security.handler.MyLogoutSuccessHandler;
import com.qwc.blog.security.properties.LoginProperties;
import com.qwc.blog.security.properties.SecurityProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

/**
 * @author qwc
 * @Date 2021/10/15 9:54
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfig {

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOriginPattern("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }

    @Bean
    @ConfigurationProperties(prefix = "jwt")
    public SecurityProperties securityProperties() {
        return new SecurityProperties();
    }

    @Bean
    @ConfigurationProperties(prefix = "login")
    public LoginProperties loginProperties() {
        return new LoginProperties();
    }

    @RequiredArgsConstructor
    public static class HttpSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {

        private final CorsFilter corsFilter;
        private final JwtAuthenticationEntryPoint authenticationEntryPoint;
        private final MyAccessDeniedHandler accessDeniedHandler;
        private final JwtAuthenticationTokenFilter authenticationTokenFilter;
        private final UserDetailsService userDetailsService;
        private final MyLogoutSuccessHandler myLogoutSuccessHandler;

        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    // 禁用CSRF
                    .csrf().disable()
                    // 异常处理
                    .exceptionHandling()
                    // 认证失败
                    .authenticationEntryPoint(authenticationEntryPoint)
                    // 鉴权失败
                    .accessDeniedHandler(accessDeniedHandler).and()
                    // 请求头处理
                    .headers()
                    // 防止iframe 造成跨域
                    .frameOptions().disable().and()
                    // 退出配置
                    .logout()
                    // 退出成功处理
                    .logoutSuccessHandler(myLogoutSuccessHandler).and()
                    // 会话管理
                    .sessionManagement()
                    // session政策
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                    // 请求认证
                    .authorizeRequests()
                    // 静态资源 等等
                    .antMatchers(
                            HttpMethod.GET,
                            "/*.html",
                            "/**/*.html",
                            "/**/*.css",
                            "/**/*.js",
                            "/webSocket/**"
                    ).permitAll()
                    .antMatchers("/common/download**").anonymous()
                    // swagger 文档
                    .antMatchers("/swagger-ui.html").permitAll()
                    .antMatchers("/swagger-ui").permitAll()
                    .antMatchers("/favicon.ico").permitAll()
                    .antMatchers("/swagger-resources/**").permitAll()
                    .antMatchers("/webjars/**").permitAll()
                    .antMatchers("/*/api-docs").permitAll()
                    // 文件
                    .antMatchers("/profile/**").permitAll()
                    .antMatchers("/file/**").permitAll()
                    // 阿里巴巴 druid
                    .antMatchers("/druid/**").permitAll()
                    // 放行OPTIONS请求
                    .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                    .antMatchers("/code", "/login").permitAll()
                    .antMatchers("/test/**").permitAll()
                    .antMatchers("/qblog/**").permitAll()
                    // 除以上请求都需要认证
                    .anyRequest().authenticated().and()
                    // 添加token校验过滤器
                    .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
                    // 跨域
                    .addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
            ;
        }

        /**
         * 强散列哈希加密实现
         */
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }

        /**
         * 身份认证接口
         */
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
        }
    }
}
